package com.adam.springredis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.ReadFrom;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Configuration
public class RedisConfig {

    @Bean
    public RedisConnectionFactory lettuceClusterConnectionFactory(RedisProperties redisProperties) {
        ClusterTopologyRefreshOptions.Builder clusterTopologyRefreshOptionsBuilder = ClusterTopologyRefreshOptions.builder();
        if(redisProperties.getLettuce().getCluster().getRefresh().isAdaptive()) {
            clusterTopologyRefreshOptionsBuilder.enableAllAdaptiveRefreshTriggers();
        }
        if(redisProperties.getLettuce().getCluster().getRefresh().getPeriod() != null) {
            clusterTopologyRefreshOptionsBuilder.adaptiveRefreshTriggersTimeout(redisProperties.getLettuce().getCluster().getRefresh().getPeriod());
        }
        clusterTopologyRefreshOptionsBuilder.enablePeriodicRefresh(Duration.ofSeconds(10));
        ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = clusterTopologyRefreshOptionsBuilder.build();

        ClientOptions clientOptions = ClusterClientOptions.builder()
                //redis命令超时时间，超时后才会使用新的拓扑信息建立连接
                .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(15)))
                .topologyRefreshOptions(clusterTopologyRefreshOptions)
                .build();

        LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder()
                .poolConfig(getPoolConfig(redisProperties.getLettuce().getPool()))
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .clientOptions(clientOptions)
//                .commandTimeout(redisProperties.getTimeout())
                .build();

        List<String> clusterNodes = redisProperties.getCluster().getNodes();
        Set<RedisNode> nodes = new HashSet<>();
        clusterNodes.forEach(node -> nodes.add(new RedisNode(node.split(":")[0], Integer.parseInt(node.split(":")[1]))));
        RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
        clusterConfiguration.setClusterNodes(nodes);
        clusterConfiguration.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());

        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(clusterConfiguration, clientConfiguration);
        return lettuceConnectionFactory;
    }

    @Bean
    @Primary
    public RedisConnectionFactory lettuceSentinelConnectionFactory(RedisProperties redisProperties) {
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(
                redisProperties.getSentinel().getMaster(), new HashSet<>(redisProperties.getSentinel().getNodes()));
        LettucePoolingClientConfiguration lettucePoolingClientConfiguration = LettucePoolingClientConfiguration.builder()
                .poolConfig(getPoolConfig(redisProperties.getLettuce().getPool()))
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .build();
        return new LettuceConnectionFactory(redisSentinelConfiguration, lettucePoolingClientConfiguration);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplateCluster(@Qualifier("lettuceClusterConnectionFactory") RedisConnectionFactory factory) {
        RedisTemplate<String,Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        postProcessRedisTemplate(template);
        return template;
    }

    @Bean
    @Primary
    public RedisTemplate<String, Object> redisTemplateSentinel(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        postProcessRedisTemplate(template);
        return template;
    }

    private void postProcessRedisTemplate(RedisTemplate<?,?> template) {
        Jackson2JsonRedisSerializer redisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        //序列化的域包括field和getter，以及修饰符包括private,protected,public
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        redisSerializer.setObjectMapper(objectMapper);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(redisSerializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(redisSerializer);
        template.afterPropertiesSet();
    }

    private GenericObjectPoolConfig<?> getPoolConfig(RedisProperties.Pool properties) {
        GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
        config.setMaxTotal(properties.getMaxActive());
        config.setMaxIdle(properties.getMaxIdle());
        config.setMinIdle(properties.getMinIdle());
        //空闲对象驱逐线程运行的时间间隔
        if (properties.getTimeBetweenEvictionRuns() != null) {
            config.setTimeBetweenEvictionRuns(properties.getTimeBetweenEvictionRuns());
        }
        //连接池连接耗尽时最大等待的时间，超过将抛异常
        if(properties.getMaxWait() != null) {
            config.setMaxWait(properties.getMaxWait());
        }
        return config;
    }

}
